package com.wy.navigator;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.AllClassesSearch;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.Query;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.stream.Collectors;

public class JSearchUtils {

    // XML工具方法
    public static boolean isSqlMapFile(XmlFile xmlFile) {
        String name = xmlFile.getName();
        if (!name.toLowerCase().endsWith(".xml")) return false;
        if (!name.toLowerCase().startsWith("sqlmap-mapping-")) return false;
        XmlTag root = xmlFile.getRootTag();
        return root != null && "sqlMap".equals(root.getName()) && root.getAttribute("namespace") != null;
    }

    /**
     * 从SQL映射XML文件中提取命名空间和操作标签
     *
     * @param xmlFile SQL映射XML文件
     * @return Pair对象，包含命名空间字符串和操作标签列表，如果文件不符合要求则返回null
     */
    public static Pair<String, List<XmlTag>> getSqlMapNamespaceAndOps(XmlFile xmlFile) {
        // 获取XML文件的根标签
        XmlTag root = xmlFile.getRootTag();

        // 检查根标签是否存在且名称为"sqlMap"，如果不是则返回null
        if (root == null || !"sqlMap".equals(root.getName())) return null;

        // 从根标签中获取"namespace"属性值
        String ns = root.getAttributeValue("namespace");

        // 如果命名空间为空则返回null
        if (ns == null) return null;

        // 使用Stream API处理根标签的所有子标签
        List<XmlTag> ops = Arrays.stream(root.getSubTags())
                // 过滤出特定的操作标签：select、insert、update、delete
                .filter(tag -> {
                    String tagName = tag.getName();
                    return "select".equals(tagName) || "insert".equals(tagName)
                            || "update".equals(tagName) || "delete".equals(tagName);
                })
                // 将过滤后的结果收集到列表中
                .distinct()
                .collect(Collectors.toList());

        // 返回包含命名空间和操作标签列表的Pair对象
        return new Pair<>(ns, ops);
    }


    public static XmlAttribute getIdAttribute(XmlTag tag) {
        return tag.getAttribute("id");
    }

    public static boolean isWebContextFile(XmlFile xmlFile) {
        String name = xmlFile.getName();
        if (!name.toLowerCase().endsWith(".xml")) return false;
        if (!name.toLowerCase().startsWith("biz-context-")) return false;
        XmlTag root = xmlFile.getRootTag();
        return root != null && "beans".equals(root.getName());
    }

    public static boolean isBizContextFile(XmlFile xmlFile) {
        String name = xmlFile.getName();
        return name.toLowerCase().endsWith(".xml") && name.toLowerCase().startsWith("biz-context-");
    }

    public static List<XmlFile> findAllXmlFilesByPrefix(Project project, String prefix) {
        GlobalSearchScope scope = GlobalSearchScope.projectScope(project);
        Collection<VirtualFile> vFiles = FilenameIndex.getAllFilesByExt(project, "xml", scope);
        PsiManager psiManager = PsiManager.getInstance(project);
        List<XmlFile> result = new ArrayList<>();

        for (VirtualFile vf : vFiles) {
            if (vf.getName().toLowerCase().startsWith(prefix.toLowerCase())) {
                PsiFile psiFile = psiManager.findFile(vf);
                if (psiFile instanceof XmlFile) {
                    result.add((XmlFile) psiFile);
                }
            }
        }
        return result;
    }

    public static List<PsiClass> findClassesExtendingByName(Project project, List<String> baseFqNameEndings) {
        // 定义搜索范围为当前项目范围
        GlobalSearchScope scope = GlobalSearchScope.projectScope(project);
        // 获取Java PSI工厂实例，用于查找类
        JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
        // 创建一个列表用于存储找到的基类
        List<PsiClass> bases = new ArrayList<>();
        // 获取项目中所有Java类
        Query<PsiClass> allClassesQuery = AllClassesSearch.search(scope, project);
        PsiClass[] allClasses = allClassesQuery.findAll().toArray(PsiClass.EMPTY_ARRAY);

        // 遍历所有类，查找符合条件的基类（类名以数组中任意字符串结尾，忽略大小写）
        for (PsiClass cls : allClasses) {
            String qualifiedName = cls.getQualifiedName();
            if (qualifiedName != null) {
                for (String ending : baseFqNameEndings) {
                    // 忽略大小写比较
                    if (qualifiedName.toLowerCase().endsWith(ending.toLowerCase())) {
                        bases.add(cls);
                        break; // 找到匹配项后跳出内层循环
                    }
                }
            }
        }

        // 如果没有找到任何有效的基类，直接返回空列表
        if (bases.isEmpty()) return Collections.emptyList();

        // 过滤掉不符合条件的类（接口、注解、枚举等），并收集为列表返回
        return bases.stream()
                .filter(cls -> cls.getQualifiedName() != null     // 必须有全限定名
                        && !cls.isInterface()                      // 不是接口
                        && !cls.isAnnotationType()                 // 不是注解类型
                        && !cls.isEnum())                          // 不是枚举类型
                .distinct()
                .collect(Collectors.toList());                     // 收集为列表
    }



    // Java继承与方法匹配
    /**
     * 查找继承自指定基类列表中任一类的所有非抽象子类。
     *
     * @param project     当前项目实例，用于确定搜索范围和获取 PSI 上下文
     * @param baseFqName  基类的完全限定名列表
     * @return 继承自这些基类的非接口、非注解、非枚举的具体类列表
     */
    public static List<PsiClass> findClassesExtending(Project project, List<String> baseFqName) {
        // 定义搜索范围为当前项目范围
        GlobalSearchScope scope = GlobalSearchScope.projectScope(project);
        // 获取Java PSI工厂实例，用于查找类
        JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
        // 创建一个列表用于存储找到的基类
        List<PsiClass> bases = new ArrayList<>();

        // 遍历传入的完全限定类名列表，查找对应的 PsiClass 实例
        for (String name : baseFqName) {
            // 根据完全限定类名和搜索范围查找PsiClass
            PsiClass cls = psiFacade.findClass(name, scope);
            // 如果找到了对应的类，则添加到bases列表中
            if (cls != null) {
                bases.add(cls);
            }
        }

        // 如果没有找到任何有效的基类，直接返回空列表
        if (bases.isEmpty()) return Collections.emptyList();

        // 使用集合存储所有继承者以避免重复
        Set<PsiClass> allInheritors = new HashSet<>();
        // 对每个基类执行继承者搜索
        for (PsiClass base : bases) {
            // 构造查询对象，搜索继承自 base 的所有类（包括间接继承）
            Query<PsiClass> query = ClassInheritorsSearch.search(base, scope, true);
            // 将查询结果添加到集合中
            allInheritors.addAll(query.findAll());
        }

        // 过滤掉不符合条件的类（接口、注解、枚举等），并收集为列表返回
        return allInheritors.stream()
                .filter(cls -> cls.getQualifiedName() != null     // 必须有全限定名
                        && !cls.isInterface()                      // 不是接口
                        && !cls.isAnnotationType()                 // 不是注解类型
                        && !cls.isEnum())                          // 不是枚举类型
                .collect(Collectors.toList());                     // 收集为列表
    }

    public static List<PsiMethod> findMethodsUsingNamespaceId(
            Project project,
            List<PsiClass> candidateClasses,
            String namespace,
            String id) {
        String target = namespace + "." + id;
        List<PsiMethod> methods = new ArrayList<>();

        for (PsiClass cls : candidateClasses) {
            // 查找类中的 NAMESPACE 常量
            String namespaceConstantValue = findNamespaceConstantValue(cls, namespace);

            if(StringUtil.isNotEmpty(namespaceConstantValue) && !namespaceConstantValue.equalsIgnoreCase(namespace)
                    && !namespaceConstantValue.equalsIgnoreCase(namespace+".")){
                continue;
            }

            for (PsiMethod method : cls.getMethods()) {
                Set<String> literals = extractStringLiteralsFolded(method);
                if (literals.contains(target)) {
                    methods.add(method);
                    continue;
                }
                if (literals.contains(namespace) && literals.contains(id)) {
                    methods.add(method);
                    continue;
                }
                // 新增情况：检查是否使用了 NAMESPACE 常量 + id 的形式
                if (namespaceConstantValue != null && literals.contains(namespaceConstantValue + "." + id)) {
                    methods.add(method);
                }
            }
        }

        return methods.stream().distinct().collect(Collectors.toList());
    }

    /**
     * 查找类中的 NAMESPACE 常量值
     *
     * @param cls 类
     * @param namespace 命名空间
     * @return NAMESPACE 常量的值，如果不存在或不匹配则返回 null
     */
    private static String findNamespaceConstantValue(PsiClass cls, String namespace) {
        for (PsiField field : cls.getAllFields()) {
            // 检查是否为名为 NAMESPACE 的常量字段
            if (Arrays.asList("sqlIdPrefix","prefixSqlId","NAMESPACE","NAME_SPACE").stream().anyMatch(name -> name.equalsIgnoreCase(field.getName()))) {
                PsiExpression initializer = field.getInitializer();
                if (initializer instanceof PsiLiteralExpression) {
                    Object value = ((PsiLiteralExpression) initializer).getValue();
                    if (value instanceof String && ((String) value).startsWith(namespace)) {
                        return (String) value;
                    }
                }
            }
        }
        return null;
    }


    /**
     * 从给定的 PsiMethod 中提取所有字符串字面量（包括通过 "+" 拼接的字符串）
     *
     * @param method 需要分析的方法
     * @return 包含所有提取出的字符串的集合
     */
    /**
     * 从给定的 PsiMethod 中提取所有字符串字面量（包括通过 "+" 拼接的字符串）
     * <p>
     * 举例说明提取过程：
     * 假设有一个方法如下：
     * <pre>
     * public void example() {
     *     String sql = "SELECT * FROM users WHERE id = " + userId + " AND name = 'John'";
     *     executeQuery("namespace" + "." + "id");
     *     String simple = "hello";
     * }
     * </pre>
     * 
     * 提取过程：
     * 1. visitLiteralExpression 会捕获所有的字符串字面量：
     *    - "SELECT * FROM users WHERE id = "
     *    - " AND name = 'John'"
     *    - "namespace"
     *    - "."
     *    - "id"
     *    - "hello"
     *
     * 2. visitBinaryExpression 会处理字符串拼接表达式：
     *    - "namespace" + "." + "id" 会被折叠成 "namespace.id"
     *
     * 最终返回的集合将包含：
     * ["SELECT * FROM users WHERE id = ", " AND name = 'John'", "namespace", ".", "id", "hello", "namespace.id"]
     *
     * @param method 需要分析的方法
     * @return 包含所有提取出的字符串的集合
     */
    private static Set<String> extractStringLiteralsFolded(PsiMethod method) {
        // 使用 LinkedHashSet 保证插入顺序并去重
        Set<String> values = new LinkedHashSet<>();

        // 创建一个访问者来遍历方法中的所有元素
        method.accept(new JavaRecursiveElementVisitor() {
            /**
             * 访问字符串字面量表达式，如 "hello"
             * 这会捕获直接的字符串常量，例如：String s = "hello";
             */
            @Override
            public void visitLiteralExpression(PsiLiteralExpression expression) {
                // 调用父类方法确保正常的遍历流程
                super.visitLiteralExpression(expression);
                // 获取字面量的值
                Object v = expression.getValue();
                // 如果是字符串类型，则添加到结果集合中
                if (v instanceof String) {
                    values.add((String) v);
                }
            }

            /**
             * 访问二元表达式，如 "hello" + "world"
             * 这会捕获通过加号拼接的字符串表达式，例如：String s = "hello" + "world";
             * 对于链式拼接如 "a" + "b" + "c"，会递归处理每个部分
             */
            @Override
            public void visitBinaryExpression(PsiBinaryExpression expression) {
                // 调用父类方法确保正常的遍历流程
                super.visitBinaryExpression(expression);
                // 只处理加法操作符（用于字符串拼接）
                if (expression.getOperationTokenType() != JavaTokenType.PLUS) return;
                // 尝试折叠（拼接）这个二元表达式
                String folded = foldStringBinary(expression);
                // 如果成功折叠，则添加到结果集合中
                if (folded != null) {
                    values.add(folded);
                }
            }
        });

        // 返回提取出的所有字符串
        return values;
    }

    private static String foldStringBinary(PsiBinaryExpression expr) {
        if (expr.getOperationTokenType() != JavaTokenType.PLUS) return null;

        String left = foldAny(expr.getLOperand());
        String right = foldAny(expr.getROperand());
        if (left == null || right == null) return null;
        return left + right;
    }

    private static String foldAny(PsiExpression e) {
        if (e instanceof PsiLiteralExpression) {
            Object v = ((PsiLiteralExpression) e).getValue();
            return v instanceof String ? (String) v : null;
        } else if (e instanceof PsiBinaryExpression) {
            PsiBinaryExpression binExpr = (PsiBinaryExpression) e;
            if (binExpr.getOperationTokenType() != JavaTokenType.PLUS) return null;
            String left = foldAny(binExpr.getLOperand());
            String right = foldAny(binExpr.getROperand());
            return left != null && right != null ? left + right : null;
        }else if (e instanceof PsiReferenceExpression) {
            // 新增处理常量引用的情况
            PsiReferenceExpression refExpr = (PsiReferenceExpression) e;
            PsiElement resolved = refExpr.resolve();
            if (resolved instanceof PsiField) {
                PsiField field = (PsiField) resolved;
                PsiExpression initializer = field.getInitializer();
                if (initializer instanceof PsiLiteralExpression) {
                    Object value = ((PsiLiteralExpression) initializer).getValue();
                    return value instanceof String ? (String) value : null;
                }
            }
        }
        return null;
    }

    // Spring XML专用方法
    public static List<XmlTag> findWebContextBeanNameDo(XmlFile xmlFile) {
        XmlTag root = xmlFile.getRootTag();
        if (root == null || !"beans".equals(root.getName())) return Collections.emptyList();

        return Arrays.stream(root.findSubTags("bean"))
                .filter(bean -> {
                    String name = bean.getAttributeValue("name");
                    return name != null && !name.isBlank() && name.endsWith(".do");
                })
                .collect(Collectors.toList());
    }

    public static String findPropertyValue(XmlTag tag, String propertyName) {
        XmlTag[] props = tag.findSubTags("property");
        for (XmlTag p : props) {
            String n = p.getAttributeValue("name");
            if (n == null || !n.equals(propertyName)) continue;

            String inline = p.getAttributeValue("value");
            if (inline != null && !inline.isBlank()) return inline;

            XmlTag childVal = p.findFirstSubTag("value");
            if (childVal != null) {
                String text = childVal.getValue().getText();
                if (text != null && !text.isBlank()) return text;
            }
        }
        return null;
    }

    public static Pair<XmlFile, XmlTag> findBizBeanById(Project project, String id) {
        List<XmlFile> files = findAllXmlFilesByPrefix(project, "biz-context-");
        for (XmlFile f : files) {
            XmlTag root = f.getRootTag();
            if (root == null || !"beans".equals(root.getName())) continue;

            for (XmlTag bean : root.findSubTags("bean")) {
                if (id.equals(bean.getAttributeValue("id"))) {
                    return new Pair<>(f, bean);
                }
            }
        }
        return null;
    }

    public static Map<String, Pair<XmlFile, XmlTag>> collectBizBeansById(Project project) {
        Map<String, Pair<XmlFile, XmlTag>> map = new HashMap<>();
        List<XmlFile> files = findAllXmlFilesByPrefix(project, "biz-context-");

        for (XmlFile f : files) {
            XmlTag root = f.getRootTag();
            if (root == null || !"beans".equals(root.getName())) continue;

            for (XmlTag tag : root.findSubTags("bean")) {
                String id = tag.getAttributeValue("id");
                if (id != null) {
                    map.put(id, new Pair<>(f, tag));
                }
            }
        }
        return map;
    }


    public static boolean endWith(@Nullable @NlsSafe String value, List<String> targetClassList) {
        return value != null && targetClassList.stream().anyMatch(value::endsWith);
    }
}